package service

import (
	models "demo/webSocket/modesl"
	"demo/webSocket/utils"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/olahol/melody"
	"strings"
	"sync"
	"time"
)

var (
	wsocket *melody.Melody

	adminClients sync.Map //管理员连接
	RefreshTimer *time.Timer
)
var RefreshSecond = 10

type WsClientType string

const (
	WsClientTypeScreen WsClientType = "screen"
	WsClientTypeAdmin  WsClientType = "admin"
)

type WsScreenClient struct {
	Session       *melody.Session
	RemoteAddress string //ip+port
	IP            string
	Port          string
	ConnectTime   string //连接进来的时间
}

// 新建screen ws 连接
func NewWsScreenClient(session *melody.Session, remoteAddress string) *WsScreenClient {
	var ip, port string
	strs := strings.Split(remoteAddress, ":")
	for i, v := range strs {
		if i == 0 {
			ip = v
		} else if i == 1 {
			port = v
		}
	}

	return &WsScreenClient{
		Session:       session,
		RemoteAddress: remoteAddress,
		IP:            ip,
		Port:          port,
		ConnectTime:   time.Now().Format("2006-01-02 15:04:05"), //time.Now().UnixNano() / 1e6,
	}
}

type WebSocketService struct {
}

func NewWebSocketService() *WebSocketService {
	return &WebSocketService{}
}

func init() {
	wsocket = melody.New()

	s := NewWebSocketService()
	/*
		建立连接
	*/
	wsocket.HandleConnect(func(session *melody.Session) {
		var err error

		defer func() {
			if err != nil {
				_ = session.Write([]byte(err.Error()))
				_ = session.Close()

			}
		}()
		token := session.Request.URL.Query().Get("token")
		/*
			其实这里就是客户端连接进来的时候判断有没有token,
			如果有就存在 WsClientTypeAdmin 这个map集合中，
			否则不携带token则存在 WsClientTypeScreen 这个map中。
			这样比如要推送登录过的用户，那么我们就推送WsClientTypeAdmin集合中的所有用户，
			反之如果没推送没有登录过的用户就可以使用 WsClientTypeScreen 集合
		*/
		if len(token) != 0 { //护士站
			//fmt.Println("有新客户端连接进来：1")
			// session.Request.RemoteAddr+":"+token
			s.StoreClient(token, session, WsClientTypeAdmin)

		}

	})

	//断开连接 这个是客户端直接关闭整个客户端系统时候调用
	wsocket.HandleDisconnect(func(session *melody.Session) {
		token := session.Request.URL.Query().Get("token")
		if len(token) != 0 {
			s.DeleteClient(token)
			//logs.Warning("disconnect:", session.Request.RemoteAddr+":"+token)
		}

	})

	// 这个应该是手动断开连接的时候调用
	wsocket.HandleClose(func(session *melody.Session, i int, st string) error {
		token := session.Request.URL.Query().Get("token")
		if len(token) != 0 {
			s.DeleteClient(token)
			fmt.Println("客户端手动断开连接,并且delete掉客户端连接池中的session，token为：", token)
			return nil
		}

		return nil
	})

	//接受消息
	wsocket.HandleMessage(func(session *melody.Session, bytes []byte) {
		fmt.Println("接受到消息：", string(bytes))
		//注意这个回调出来的session 只是针对他自己的客户端
		//_ = session.Write(bytes)
		//if err != nil {
		//	logs.Error("心跳包发送失败：", err)
		//}
	})

}

func (s *WebSocketService) GetWSocket() *melody.Melody {
	return wsocket
}

func (s *WebSocketService) StoreClient(key string, session *melody.Session, clientType WsClientType) {
	if clientType == WsClientTypeAdmin {
		adminClients.Store(key, session)
	}

	return
}

func (s *WebSocketService) DeleteClient(key string) {
	adminClients.Delete(key)
	return
}

/*
获取一个用户的链接session
*/
func (s *WebSocketService) GetClientSession(token string) (clients *melody.Session) {
	clientsInter, ok := adminClients.Load(token)
	if !ok {
		return nil
	} else {
		clients = clientsInter.(*melody.Session)
	}
	return
}

/*
	广播所有用户（推荐使用这个）
*/

func (s *WebSocketService) BoardCastAllUser(data models.WsMessage) (err error) {
	data.Model = models.WsModelAdmin
	var msg []byte
	if msg, err = utils.StructToJson(data); err != nil {
		return
	}
	adminClients.Range(func(key, client interface{}) bool {
		//先判断该用户是否离线。
		if !client.(*melody.Session).IsClosed() {
			if err = client.(*melody.Session).Write(msg); err != nil {
				return true
			}
		}
		return true
	})

	return
}

/*
指定多个用户推送，但又不是全部用户。
*/
func (s *WebSocketService) BoardCastDesignateUser(token []string, data models.WsMessage) (err error) {
	data.Model = models.WsModelAdmin
	var msg []byte
	if msg, err = utils.StructToJson(data); err != nil {
		return
	}

	for _, v := range token {
		value, ok := adminClients.Load(v)
		if ok {
			if !value.(*melody.Session).IsClosed() {
				err := value.(*melody.Session).Write(msg)
				if err != nil {
					return errors.New("消息写入失败，token为：" + v)
				}
			}
		}
	}

	return
}

/*
针对单个用户推送
*/
func (s *WebSocketService) BoardCastOneUser(token string, data models.WsMessage) (err error) {
	var (
		msg []byte
	)
	if msg, err = json.Marshal(data); err != nil {
		return
	}
	err = s.BC(msg, token)
	return
}

func (s *WebSocketService) BC(msg []byte, token string) (err error) {
	clients := s.GetClientSession(token)
	if clients == nil {
		return errors.New("没有该客户端")
	}
	if clients.IsClosed() {
		s.DeleteClient(token)
		return errors.New("消息发送失败，客户端已经断开连接,客户端token为：" + token)
	}
	errs := clients.Write(msg)
	if errs != nil {
		return errors.New("消息发送失败：" + errs.Error())
	}
	return nil
}

/*
注意：这里是向所session，是session而不是token 发送通知（一般不用）
*/
func (s *WebSocketService) BoardCastAllMessage(data models.WsMessage) (err error) {
	var msg []byte
	if msg, err = utils.StructToJson(data); err != nil {
		return
	}
	err = wsocket.Broadcast(msg)
	return
}

// 检测有队列中有多少用户
func (s *WebSocketService) SetupTimer() {

	RefreshTimer = time.NewTimer(time.Duration(RefreshSecond) * time.Second)
	go func() {
		for {
			select {
			case <-RefreshTimer.C:
				//data := s.GetClients(WsClientTypeAdmin)
				count := 0
				adminClients.Range(func(key, value interface{}) bool {
					//先判断该用户是否离线。
					if !value.(*melody.Session).IsClosed() {
						count++
						//心跳包
						_ = value.(*melody.Session).Write([]byte("heartbeat"))
						return true
					} else {
						//定时清清理断开连接的用户。
						adminClients.Delete(key)
					}

					return true
				})
				fmt.Printf("检测到队列现有 %d 个客户端\n", count)
				//超时后重置定时器
				RefreshTimer.Reset(time.Duration(RefreshSecond) * time.Second)
			}
		}

	}()
}

// 超时后重置定时器
func (s *WebSocketService) Refresh() {
	RefreshTimer.Reset(0)
}
